iT邦幫忙

2024 iThome 鐵人賽

DAY 12
0
Modern Web

一起來玩圖像編輯器:Fabric.js 的實戰修煉系列 第 12

Day12- fabric.js canvas下載圖片不難,麻煩的東西是CORS!

  • 分享至 

  • xImage
  •  

fabric.js 下載圖片的方式

使用 canvas.toDataURL 再加上一些設置參數

<button id="download">下載</button>
// 下載圖片
const onDownloadImage = () => {

	const data = canvas.toDataURL({
		format: 'jpeg', // jpeg | png
		quality: 1, // 0-1 之間
		multiplier: 2,
		
		top: 0.5,
		left: 0.5,
		width:200,
		height: 100,
	});
	
	//創建下載
	const a = document.createElement('a');
	a.href = imageOutput;
	a.download = 'drawing.jpg';
	a.click();
}


document.getElementById("download").addEventListener("click", onDownloadImage);

canvas.toDataURL 常用參數

  • left, top, width, height:指定要輸出的畫布區域。
  • format:指定輸出圖像的格式。
    • 值:'jpeg', 'png', 'webp' 等
    • 預設:'png'
  • quality:指定圖像的質量(僅適用於 jpeg 和 webp)。
    • 值:0 到 1 之間
    • 預設:0.8
  • multiplier:應用於畫布尺寸的乘數
    ex:
    如果你覺得圖像的大小不夠的話可以設置這個
canvas.toDataURL({ multiplier: 2 }); 
// 輸出 2 倍大小的圖像

如果原尺寸是200px * 100px
使用 multiplier: 2 尺寸就會變成 400px * 200px

使用 changeDpiDataUrl 套件 來改變 dpi

changedpi - npm (npmjs.com)

如果需求有需要輸出成特定的 dpi
changeDPI 通過分離、操作和重新組合圖像文件的標頭,實現了快速且不改變圖像內容的 DPI 更改。

如果不了解 dpi 的話可以看這篇:
dp, px, pt, dpi, ppi 傻傻分不清!. 最實用的 UI 長度規則! | by Kuang Lin | Medium

const imageOutput = changeDpiDataUrl(data, 300); // 設置 300dpi

issue!出現如果你按了下載圖片的按鈕卻沒反應

然後來到今天的大魔王,如果你按了下載圖片的按鈕卻沒反應
且 主控台出現這個

Uncaught DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.

https://ithelp.ithome.com.tw/upload/images/20240815/20168354p0tCht0x5I.png

於是乎,問題來了

為什麼圖片在網頁 <img>還有canvas上放可以瀏覽,但無法輸出or下載 canvas 內容成圖片?

這涉及瀏覽器的同源政策(Same-Origin Policy)和跨域資源共享(CORS)機制

  1. 瀏覽器對 <img> 標籤和 canvas 的安全策略不同。<img>標籤可以顯示跨域圖片,但不允許 JavaScript 訪問其像素數據。

  2. Canvas更嚴格,需要圖片資源明確允許跨域訪問。
    這是為了防止未經授權的網站讀取且再製其他網站圖片的像素數據,因為可能洩露敏感信息。

所以當你試圖在canvas中繪製跨域圖片時,如果該圖片伺服器沒有設置正確的 CORS 響應頭,瀏覽器會阻止操作並拋出安全錯誤。
就是上面看到的錯誤訊息。

這時你可以打開你的圖片來源的網址的 devtool 裡面的 網路
看看裡面的回應標頭
https://ithelp.ithome.com.tw/upload/images/20240815/20168354dy00zKSFky.png

簡而言之:
如果打開你的圖片來源的 Access-Control-Allow-Origin 沒有設置,就是不能在 canvas 上被輸出!

結果需要兩步驟解決

1. 要把檔案 fetch 之後,並先存成 blob 連結格式

[!URL.createObjectURL()]
URL.createObjectURL() 是一個靜態方法,它會生成一個包含指定 Blob 對象(包括 File 對象)數據的 DOMString(將 Blob 或 File 對象轉換為 URL)。
這個 URL 指向瀏覽器內存中的對象,而不是網路位置。
這個 URL 可以用作 的 src 屬性或下載 Blob 對象的來源。

這個 URL 是臨時的,只在創建它的頁面生命週期內有效。如果需要長期存儲,可以考慮使用 IndexedDB。

URL.createObjectURL()是淺拷貝或是深拷貝嗎?

URL.createObjectURL() 既不是淺拷貝也不是深拷貝。這個方法會創建一個指向輸入的 Blob 或 File 對象的 URL。這個 URL 是一個新的字符串,但它並不包含 Blob 或 File 對象的任何數據,而只是提供了一種方式來訪問這些數據。因此,這個方法並不會複製 Blob 或 File 對象的數據,也不會修改這些數據。

更多關於 Blob:[WebAPIs] Blob, File 和 FileReader | PJCHENder 未整理筆記

fetch(url)
	.then((result) => result.blob())
	.then((blob) => {
		const url = URL.createObjectURL(blob);
		fabric.Image.fromURL(url, function (Image) {
			editor?.canvas.setBackgroundImage(
				Image,
				editor?.canvas.renderAll.bind(editor?.canvas)
			);
			editor?.canvas.renderAll();
		});
	})
	.catch((err) => {
		console.log(err);
	});

解法來源:fabricjs2 - FabricJS - "Tainted canvases may not be exported" JS Error when run toDataURL - Stack Overflow

2. 確保你的圖片的 Access-Control-Allow-Origin 有設置成你的網域

這部分的解法有多種情況,可依自己的情形選擇解法:

情況1:如果你的圖片是放在自家 server 上 or 你可以控制圖片伺服器時

請後端在圖片伺服器端設置正確的CORS響應頭。
(如果中間有使用 CDN 服務,也要在 CDN 上也要設置喔)

例如:
使用通配符 *

Access-Control-Allow-Origin: *

或者指定允許的域名,例如前端正式站網址(但這樣測試站就會無法使用)

Access-Control-Allow-Origin: 'www.my-frontend-domain.com'
Access-Control-Allow-Origin 有單一值限制

標準的 Access-Control-Allow-Origin 響應頭確實一次只能設置一個值。
這可以是一個特定的域名,或者是通配符 *

這算最正統(? 的做法
但如果你無法控制圖片伺服器,請用以下的方法

情況2:使用代理伺服器:在你的域名下創建一個代理,轉發圖片請求,這樣就不存在跨域問題。

使用代理伺服器的方式:
前端开发服务器中的 Proxy 代理跨域实现原理解读在前端的开发过程中,尤其是在浏览器环境下,跨域是个绕不开的话题,相信 - 掘金 (juejin.cn)

情況3:如果是開發環境,可以考慮使用瀏覽器 extension,暫時禁用瀏覽器的同源策略(不推薦用於生產環境)。

有些瀏覽器 extension可以輕鬆切換 CORS 設置,如 Chrome 的 "Allow CORS: Access-Control-Allow-Origin" extension。
(我之前有嘗試這種方式但沒成功)

情況4:使用第三方圖片網站作為來源

把圖片上傳到 imgur 這類的第三方圖片網站,這類的網站通常有設置 Access-Control-Allow-Origin: *

情況5:如果可能,將圖片放在同一域名下,避免跨域問題。

如果圖片不多 or 不需要用到 api,可以把圖片放在前端專案裡,這樣就絕對同源了。


p.s.
以上問題的其他解法:
使用 {crossOrigin: 'anonymous'}加在 .fromURL 之後無效
這是舊方法(2018之前的 fabric.js 版本使用有效)


今天篇幅有點長,之前花很多時間在解這個
分享給大家避免大家也踩坑QAQ

附上使用第三方網站圖源的例子🌰:
codepen-下載圖片來源為連結的 fabric.js 畫布


上一篇
Day11-fabricjs 的花式圖片上傳、濾鏡使用
下一篇
Day13- fabric.js 的圖層與群組管理
系列文
一起來玩圖像編輯器:Fabric.js 的實戰修煉30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言